﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace UI.Control
{
    // 2 - Mise à jour des Covers
    // 3 - Ouverture d'un item et affichage de la liste + play
    // 4 - Animation on mouse over
    public partial class KioskRow : UserControl
    {

        #region Variables

        // number of data item to bypass at starting.
        private const int BYPASS = 1;

        private int _rowIndex;

        private Kiosk _kiosk;

        //---- We recycle the thumbnails
        private List<KioskThumbnail> _recycleCache = new List<KioskThumbnail>();

        //---- Update
        private object _lockUpdate = new object();
        private bool _mustUpdate = false;

        //---- Scroll
        private DateTime _mouseDownTime;
        private Point _mouseDragStartPoint;
        private Point _mouseDragLastPoint;
        private Point _mouseDragCurrentPoint;
        private DoubleAnimation _scrollAnimation = new DoubleAnimation();

        private const double DECELERATION = 980;
        private const double SPEED_RATIO = .5;
        private const double MAX_VELOCITY = 2500;
        private const double MIN_DISTANCE = 0;
        private const double TIME_THRESHOLD = .4;

        #endregion

        #region Constructor

        public KioskRow(Kiosk kiosk, int rowIndex)
        {
            InitializeComponent();
            borTouch.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(KioskRow_PreviewMouseLeftButtonDown);
            borTouch.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(KioskRow_PreviewMouseLeftButtonUp);
            borTouch.PreviewMouseMove += new MouseEventHandler(KioskRow_PreviewMouseMove);
            borTouch.MouseLeave += new MouseEventHandler(borTouch_MouseLeave);

            _scrollAnimation.Completed += new EventHandler(scrollAnimation_Completed);
            translateTransform.Changed += new EventHandler(translateTransform_Changed);

            _kiosk = kiosk;
            _rowIndex = rowIndex;

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
        }

        #endregion

        #region OnReset

        internal void OnReset()
        {
            CancelScrollAnimation();
            translateTransform.X = 0;
        }

        #endregion

        #region OnUpdate

        internal void OnUpdate()
        {
            lock (_lockUpdate)
                _mustUpdate = true;
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);

            lock (_lockUpdate)
                if (_mustUpdate)
                {
                    OnUpdateContent();
                    _mustUpdate = false;
                }

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
        }

        private void OnUpdateContent()
        {
            int itemsPerRow = (int)((_kiosk.DataSource.Count - BYPASS) / _kiosk.Rows);
            int startIndex = itemsPerRow * _rowIndex;

            // The last row can contains more than other rows
            if (_rowIndex < ((_kiosk.DataSource.Count - BYPASS) % _kiosk.Rows))
                itemsPerRow++;

            int thumbnailsToDisplay = Math.Min(itemsPerRow, (int)(_kiosk.ActualWidth / _kiosk.ItemWidth) + 1 + BYPASS);

            int translateIndex = (int)(-translateTransform.X / _kiosk.ItemWidth);

            //---- Check if all the thumbnails are created
            for (int index = container.Children.Count; index < thumbnailsToDisplay; index++)
            {
                KioskThumbnail thumbnail = new KioskThumbnail(_kiosk);
                thumbnail.Width = _kiosk.ItemWidth;
                thumbnail.Height = _kiosk.ItemHeight;
                _recycleCache.Add(thumbnail);
            }

            //---- Remove the thumbnails that should not be displayed.
            int childrenCount = container.Children.Count - 1;
            for (int index = childrenCount; index >= thumbnailsToDisplay; index--)
                container.Children.RemoveAt(index);

            //---- Remove the thumbnails if the are out of the view
            for (int index = container.Children.Count - 1; index > -1; index--)
            {
                KioskThumbnail thumbnail = null;

                //----- Calculation
                double x = translateTransform.X + index * _kiosk.ItemWidth;

                if (index < container.Children.Count)
                    thumbnail = (KioskThumbnail)container.Children[index];

                if (thumbnail == null)
                    continue;

                //---- Right
                int right = (int)(translateTransform.X + Canvas.GetLeft(thumbnail) + _kiosk.ItemWidth);
                if (right < 0)
                {
                    _recycleCache.Add(thumbnail);
                    container.Children.Remove(thumbnail);
                }

                //---- Left
                int left = (int)(translateTransform.X + Canvas.GetLeft(thumbnail));
                if (left > _kiosk.ActualWidth)
                {
                    _recycleCache.Add(thumbnail);
                    container.Children.RemoveAt(index);
                }
            }

            //---- Update the thumbails
            for (int index = 0; index < thumbnailsToDisplay; index++)
            {
                int realIndex = ((translateIndex + index) * _kiosk.Rows) + _rowIndex;

                if (realIndex >= (_kiosk.DataSource.Count - BYPASS))
                    break;

                AlbumCoverItem item = _kiosk.DataSource[BYPASS + realIndex];

                double x = (translateIndex + index) * _kiosk.ItemWidth;

                // Search for this thumbnails
                KioskThumbnail thumbnail = null;
                if (index < container.Children.Count)
                    thumbnail = (KioskThumbnail)container.Children[index];

                // Recycle the thumbnails
                if (thumbnail == null && _recycleCache.Count > 0)
                {
                    thumbnail = _recycleCache[_recycleCache.Count - 1];
                    _recycleCache.RemoveAt(_recycleCache.Count - 1);
                    thumbnail.Item = item;
                    container.Children.Add(thumbnail);
                }

                // Create a new thumbnails
                if (thumbnail == null)
                {
                    thumbnail = new KioskThumbnail(_kiosk);
                    thumbnail.Width = _kiosk.ItemWidth;
                    thumbnail.Height = _kiosk.ItemHeight;
                    container.Children.Add(thumbnail);
                }

                // Position the thumbnails
                if (thumbnail.Item == null || !item.Equals(thumbnail.Item))
                    thumbnail.Item = item;
                if (Canvas.GetLeft(thumbnail) != x)
                    Canvas.SetLeft(thumbnail, x);
            }
        }

        #endregion

        #region Mouse events

        void borTouch_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!IsMouseCaptured)
                return;

            ReleaseMouseCapture();
            OnMoveRow(e);
        }

        void KioskRow_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (!borTouch.IsMouseCaptured)
                return;

            // Stop the current animation
            CancelScrollAnimation();

            _mouseDragCurrentPoint = e.GetPosition(this);

            double totalTranslation = CalculateTotalTranslation(_mouseDragCurrentPoint.X - _mouseDragLastPoint.X);

            translateTransform.X = totalTranslation;

            _mouseDragLastPoint = _mouseDragCurrentPoint;

            OnUpdate();
        }

        void KioskRow_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!borTouch.IsMouseCaptured)
                return;

            borTouch.ReleaseMouseCapture();
            OnMoveRow(e);
        }

        void KioskRow_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _mouseDownTime = DateTime.Now;
            _mouseDragStartPoint = e.GetPosition(this);
            _mouseDragLastPoint = _mouseDragStartPoint;
            borTouch.CaptureMouse();
        }

        #endregion

        #region OnMouseDoubleClick

        internal void OnMouseDoubleClick(MouseButtonEventArgs e)
        {
            foreach (KioskThumbnail thumbnail in container.Children)
                if (thumbnail.InputHitTest(e.GetPosition(thumbnail)) != null)
                {
                    thumbnail.OpenAlbumDialog();
                    return;
                }
        }

        #endregion

        #region Scroll: OnMoveRow

        private void OnMoveRow(MouseEventArgs e)
        {
            double deltaX = e.GetPosition(this).X - _mouseDragStartPoint.X;
            double totalTranslation = CalculateTotalTranslation(deltaX);

            DoScrollAnimation(totalTranslation, deltaX);
        }

        #endregion

        #region Scroll : CalculateTotalTranslation

        private double CalculateTotalTranslation(double delta)
        {
            double translation = translateTransform.X + delta;

            int rowWidth = (int)((_kiosk.DataSource.Count - BYPASS) / _kiosk.Rows) * _kiosk.ItemWidth;

            // The last row can contains more than other rows
            if (_rowIndex < ((_kiosk.DataSource.Count - BYPASS) % _kiosk.Rows))
                rowWidth += _kiosk.ItemWidth;

            if (rowWidth + translation - _kiosk.ItemWidth < 0)
                translation = -rowWidth + _kiosk.ItemWidth;

            if (translation > 0)
                translation = 0;

            return translation;
        }

        #endregion

        #region Scroll : AnimateRow

        private void DoScrollAnimation(double translation, double delta)
        {
            DateTime startTime = _mouseDownTime;
            DateTime endTime = DateTime.Now;

            //----
            double timeScrolled = endTime.Subtract(startTime).TotalSeconds;

            //if scrolling slowly, don't scroll with force
            if (timeScrolled >= TIME_THRESHOLD)
                return;

            int direction = 1;
            if (delta < 0)
                direction = -1;

            double distanceScrolled = Math.Max(Math.Abs(delta), MIN_DISTANCE);

            double velocity = distanceScrolled / timeScrolled;
            velocity = Math.Min(MAX_VELOCITY, velocity);

            double timeToScroll = (velocity / DECELERATION) * SPEED_RATIO;

            double distanceToScroll = ((velocity * velocity) / (2 * DECELERATION)) * SPEED_RATIO;

            // Stop the current animation
            CancelScrollAnimation();

            // Restart a new one
            _scrollAnimation.BeginTime = TimeSpan.Zero;
            _scrollAnimation.From = translateTransform.X;
            _scrollAnimation.To = CalculateTotalTranslation(distanceToScroll * direction);
            _scrollAnimation.DecelerationRatio = .9;
            _scrollAnimation.SpeedRatio = SPEED_RATIO;
            _scrollAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, Convert.ToInt32(timeToScroll), 0));

            translateTransform.BeginAnimation(TranslateTransform.XProperty, _scrollAnimation);
        }

        #endregion

        #region Scroll : CancelScrollAnimation

        private void CancelScrollAnimation()
        {
            double translationX = translateTransform.X;

            // Stop the current animation
            // BeginAnimation will destroy any existing clocks if it is passed 'null' for the AnimationTimeline parameter.
            translateTransform.BeginAnimation(TranslateTransform.XProperty, null, HandoffBehavior.SnapshotAndReplace);

            translateTransform.X = translationX;
        }

        #endregion

        #region Scroll : Animation_Completed

        void translateTransform_Changed(object sender, EventArgs e)
        {
            OnUpdate();
        }

        void scrollAnimation_Completed(object sender, EventArgs e)
        {
            CancelScrollAnimation();
            OnUpdate();
        }

        #endregion

    }
}
